/**************************************************************
 * main.c - interface to all
 * Copyright (c) 2009  Qin Xiaoxue  <crs.chin@gmail.com>
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 * 
 **************************************************************/


#include <stdarg.h>
#include <dirent.h>
#include <dlfcn.h>
#include <errno.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <readline/readline.h>

#include "common.h"
#include "main.h"
#include "ui.h"
#include "client.h"
#include "fetchlyric.h"
#include "lyricparse.h"
#include "metainfo.h"

//#include "conffile.h"  // deppreciated.
#include "configfile.h"
#include "encoding_convert.h"


struct submode{
  void *handle;
  const gchar *path;
  const gchar *name;
  const gchar *descrip;
};

struct util_mode{
  const gchar *name;
  const gchar *description;
  gint (*fn)(gint argc, gchar **argv);
};

struct command{
  const gchar *short_cmd;
  const gchar *long_cmd;
  const gchar *descrip;
  gint (*fn)(void *arg);
  gint flag;
};

/* if lyric is not NULL, fetch lyric plugins will not be loaded from
   sysdir, the program is using plugin of this */
static const gchar *lyric_plugin = NULL;
static const gchar *player_plugin = NULL;
static const gchar *lyric_plugin_file = NULL;
static const gchar *player_plugin_file = NULL;
static const gchar *mode = "cli";

static GList *modes = NULL;


static struct ui_descrip *load_ui_file(const gchar *path, void **handle)
{
  void *h;
  struct ui_descrip *descrip;

  if(! g_file_test(path, G_FILE_TEST_IS_REGULAR))
	return NULL;

  h = dlopen(path, RTLD_LAZY);
  if(! h)  {
	print_verb("%s can't be loaded, aborted: %s\n", path, dlerror());
	return NULL;
  }

  descrip = dlsym(h, UI_EXPORT_SYMBOL_NAME);
  if(! descrip)  {
	print_info("malformat ui plugin %s, aborted.\n", path);
	dlclose(h);
	return NULL;
  }

  if(handle)
	*handle = h;
  return descrip;
}

static gint __mode_find(gconstpointer a, gconstpointer b)
{
  struct submode *_a = (struct submode *)a;
  const gchar *_b = (const gchar *)b;

  return strcmp(_a->name, _b);
}


static struct submode *find_ui(GList *list, const gchar *name)
{
  GList *found;

  if(! list || ! name)
	return NULL;
  found = g_list_find_custom(list, name, __mode_find);
  if(! found)
	return NULL;

  return (struct submode *)(found->data);
}


static struct ui_descrip *load_ui(struct submode *mode)
{
  if(! mode)
	return NULL;

  if(mode->handle)
	return (struct ui_descrip *)(dlsym(mode->handle, UI_EXPORT_SYMBOL_NAME));

  return load_ui_file(mode->path, &mode->handle);
}


static struct ui_descrip *load_ui_name(GList *list, const gchar *name)
{
  GList *l = list;
  struct submode *mode;

  if(! list || ! name)
	return NULL;

  do{
	mode = (struct submode *)l->data;
	if(! strcmp(name, mode->name))
	  return load_ui(mode);
  }while(l = g_list_next(l));

  return NULL;
}

/* 
 *return -1 for serious error
 */
static gint ui_run(struct ui_descrip *ui_descrip, const gchar *args, int argc, char **argv)
{
  struct ui *ui;
  gint ret;

  ui = (ui_descrip->ui_init)(args, argc, argv);
  if(! ui)
	return -1;
  ret = (ui->ui_run)(ui->data);
  (ui->ui_cleanup)(ui->data);

  return ret;
}



static void unload_ui(struct submode *mode)
{
  if(! mode || ! mode->handle)
	return;

  dlclose(mode->handle);
  mode->handle = NULL;
}

static void unload_ui_all(GList *list)
{
  if(! list)
	return;

  do
	unload_ui((struct submode *)(list->data));
  while(list = g_list_next(list));
}


static GList *build_mode_list(const gchar *dir)
{
  const char *defdir = UI_BASEDIR;
  struct dirent *ent;
  DIR *d;
  GList *list = NULL;
  gchar *path;
  struct ui_descrip *ui;
  void *handle;
  struct submode *mode;

  if(dir)
	defdir = dir;

  d = opendir(defdir);
  if(! d)
	return NULL;

  while(ent = readdir(d))  {
	path = g_strdup_printf("%s/%s", defdir, ent->d_name);
	ui = load_ui_file(path, &handle);
	if(ui)  {
	  if(! g_list_find_custom(list, ui->name, __mode_find))  {
		mode = g_new(struct submode, 1);
		mode->handle = NULL;
		mode->path = path;
		mode->name = strdup(ui->name);
		mode->descrip = strdup(ui->description);
		list = g_list_prepend(list, mode);
	  }
	  dlclose(handle);
	}
  }

  closedir(d);

  return list;
}


static GList *destroy_mode_list(GList *list)
{
  struct submode *mode;

  if(! list)
	return;

  unload_ui_all(list);

  do{
	mode = (struct submode *)(list->data);
	if(mode->path)
	  g_free((void *)mode->path);
	if(mode->name)
	  g_free((void *)mode->name);
	if(mode->descrip)
	  g_free((void *)mode->descrip);
	g_free(mode);

	list = g_list_remove(list, mode);
  }while(list);

  return list;
}


static void greeting(void)
{
  if(! quiet)
	return;

  printf(PROG_NAME " " PROG_VERSION " Copyright (c) 2009 All right reserved.\n"
		 GREETING);
}


static gint encoding_convert_mode(gint argc, gchar **argv);
static gint lyric_search_mode(gint argc, gchar **argv);
static gint id3_sanify_mode(gint argc, gchar **argv);


/* builtin utility mode list */
static struct util_mode utils[] = {
  { "iconvert", "encoding convert mode", encoding_convert_mode },
  { "lyrics", "lyric search mode", lyric_search_mode },
  { "idsane", "mp3 id3(v1) sanify mode", id3_sanify_mode },
  /* newly sopported here */
  { NULL, NULL }
};


#define do_if_option(arg,exec)					\
  do{											\
	if(arg[0] == '-')							\
	  exec();									\
  }while(0)

static void __attribute__((noreturn)) 	encoding_convert_mode_help()
{
  fprintf(stderr,
		  "ICONVERT_MODE_COMMAND -h\n"
		  "ICONVERT_MODE_COMMAND [-f FROM] [-t TO] [-o OUTPUT] [FILE]\n"
		  "   convert fie encodings\n"
		  "OPTIONS:\n"
		  "\t-f    specify original encoding of FILE, guess it if omitted\n"
		  "\t-t    specify target encoding, locale depended, zh_GB18030 defaulted\n"
		  "\t      for zh_CN locales\n"
		  "\t-l    locales guessing, zh_CN defaulted\n"
		  "\t-k    keep convert if invalid encoding encountered\n"
		  "\t-h    show this help message\n"
		  "\t-o    output file for the converted, stdout if omitetd\n"
		  "\tFILE  input file to convert, stdin defaulted if omitted\n");

  exit(-1);
}


static gint encoding_convert_mode(int argc, char **argv)
{
  const gchar *from_encode = NULL, *to_encode = NULL;
  /*config_t *config;  configurable in the future */
  FILE *from, *to;
  const gchar *inpath = NULL;
  const gchar *outpath = NULL;
  const gchar *lang = "zh_CN";
  gchar *addr, *buf;
  gint fsize, size;
  gboolean keep = FALSE;
  gint ret = 0;

  for(; argc--; argv++)  {
	if(argv[0][0] != '-')  {
	  if(inpath)
		encoding_convert_mode_help();
	  else
		inpath = argv[0];
	  continue;
	}

	if(! strcmp(argv[0], "-f"))  {
	  from_encode = argv[1];
	  do_if_option(from_encode, encoding_convert_mode_help);
	  argc--;
	  argv++;
	}else 	if(! strcmp(argv[0], "-t"))  {
	  to_encode = argv[1];
	  do_if_option(to_encode, encoding_convert_mode_help);
	  argc--;
	  argv++;
	}else 	if(! strcmp(argv[0], "-l"))  {
	  lang = argv[1];
	  do_if_option(lang, encoding_convert_mode_help);
	  argc--;
	  argv++;
	}else 	if(! strcmp(argv[0], "-k"))  {
	  keep = TRUE;
	}else 	if(! strcmp(argv[0], "-o"))  {
	  outpath = argv[1];
	  do_if_option(outpath, encoding_convert_mode_help);
	  argc--;
	  argv++;
	}else 	if(! strcmp(argv[0], "-h"))
	  encoding_convert_mode_help();
	else
	  encoding_convert_mode_help();
  }

  if(inpath)  {
	from = fopen(inpath, "r");
	if(! from)  {
	  print_err("failed to open file %s: %s\n", inpath, strerror(errno));
	  exit(1);
	}
  }else  {
	from = tmpfile();
	if(! from)  {
	  print_err("can not create temperary file.\n");
	  exit(1);
	}

	if(! file_copy(stdin, from))  {
	  print_err("failed to copy from stdin: %s\n", strerror(errno));
	  exit(1);
	}
  }

  if(outpath)  {
	to = fopen(outpath, "w");
	if(! to)  {
	  print_err("failed to open file %s for writing: %s\n", to, strerror(errno));
	  exit(1);
	}
  }else
	to = stdout;

  if(! from_encode)  {
	fseek(from, 0, SEEK_END);
	fsize = ftell(from);
	addr = (gchar *)mmap(NULL, fsize, PROT_READ, MAP_PRIVATE, fileno(from), 0);
	if(addr != MAP_FAILED)  {
	  buf = addr;
	  size = fsize;
	}else  {
	  addr = NULL;
	  rewind(from);
	  buf = g_new(char, 1024*8);
	  size = fread(buf, 1, 1024*8, from);
	}

	from_encode = guess_encoding(buf, size, lang, ENCA_NAME_STYLE_ICONV);

	if(addr)
	  munmap(addr, fsize);
	else
	  g_free(buf);

	if(! from_encode)  {
	  print_err("failed to guess encoding from the file contents!\n"
				"\tplease specify it from the command line.\n");
	  exit(1);
	}
  }

  /* FIXME: default if only valid under zh_CN locale */
  if(! to_encode)
	to_encode = "utf-8";

  if(! encoding_convert_to(from, to, from_encode, to_encode, keep))  {
	print_err("failed to convert file from encoding %s to %s!\n",
			  from_encode, to_encode);
	if(! keep)
	  fprintf(stderr, "\tthere may be invalid coding sequence in the file.\n");
	ret = 1;
  }

  if(from != stdin)
	fclose(from);
  if(to != stdout)
	fclose(to);

  return ret;
}


static void __attribute__((noreturn)) 	lyric_search_mode_help()
{
  fprintf(stderr,
		  "LYRICS_MODE_COMMAND -h\n"
		  "LYRICS_MODE_COMMAND [-a ARTIST] -t TITLE [-s] [-o OUTPUT]\n"
		  "   Search the lyrics according given infomation\n"
		  "OPTIONS:\n"
		  "\t-a    specify artist of the song, optional\n"
		  "\t-u    auto select lyrics from candidates\n"
		  "\t-t    title of the song, mandatory\n"
		  "\t-s    write the lyric in the format of subtitle(srt)\n"
		  "\t-h    show this help message\n"
		  "\t-o    file for saving the lyric\n");

  exit(-1);
}


static gint prompt_for_index(gint from, gint to)
{
  gint i = 0;
  gchar *str;
  const gchar *prompt;

  putchar('\n');
  prompt = g_strdup_printf("\rselect lyric index within(%d,%d):>", from, to);
  while(str = readline(prompt))  {
    if(sscanf(str, "%d", &i))
      if(i >= from && i <= to)
		break;
    g_free(str);
    printf("Error, out of range or illegal.\n");
  }

  g_free(str);
  g_free((void *)prompt);
  return i;
}


static gint __get_term_width()
{
  struct winsize sz;

  if(ioctl(1, TIOCGWINSZ, &sz) == -1)
    return 0;

  return sz.ws_col;
}


static gboolean __is_illegal(gchar c, const gchar *str)
{
  while(*str)
	if(c == *str++)
	  return TRUE;

  return FALSE;
}

static gchar  *__sanify(gchar *str)
{
  gchar *p, *i, *j;

  if(! str)
    return NULL;

  p = g_new0(gchar, strlen(str) + 1);
  i = p;
  j = str;
  while(*j)  {
    if(! __is_illegal(*j, " \t~`!@#$%^&*()-_+=|\\{}[]:\";\'<>?,./"))  {
	  *i = *j;
	  i++;
	}
    j++;
  }

  strcpy(str, p);
  g_free(p);

  return str;
}


static FILE *prompt_pick_lyric(GSList *list, const gchar *title, const gchar *artist)
{
  GSList *iter = list;
  struct lyric_url *info;
  gint title_len = 0, artist_len = 0, album_len = 0;
  gint index = 0, tmp, term_width;
  FILE *lyric_file = NULL;
  lyric_t *lyric = NULL;
  gchar *_artist;

  if(! list)
    return NULL;

  do{
    info = (struct lyric_url *)iter->data;
    if(info->title)  {
      tmp = strlen(info->title);
      if(tmp > title_len)
		title_len = tmp;
    }

    if(info->artist)  {
      tmp = strlen(info->artist);
      if(tmp > artist_len)
		artist_len = tmp;
    }

    if(info->album)  {
      tmp = strlen(info->album);
      if(tmp > album_len)
		album_len = tmp;
    }
  }while(iter = g_slist_next(iter));

  iter = list;
  tmp = 1;
  term_width = __get_term_width();
  title_len++;
  artist_len++;
  album_len++;
  if(! term_width || term_width <= (title_len + artist_len + album_len + 13))  {
    /* if terminal unknown, assume it big enough */
    printf("\n%5s%*s    %*s    %*s\n", "NO.", title_len, "TITLE",
		   artist_len, "ARTIST", album_len, "ALBUM");
    do{
      info = (struct lyric_url *)iter->data;
      printf("[%-2d] %-*s   %-*s    %-*s\n", tmp++,
			 title_len, info->title ?: "Unknown",
			 artist_len, info->artist ?: "Unknown", 
			 album_len, info->album ?: "Unknown");

    }while(iter = g_slist_next(iter));
  }else  {
    printf("\n%-5s%-*s    %-*s\n", "NO.", title_len, "TITLE", artist_len, "ARTIST");
    do{
      info = (struct lyric_url *)iter->data;
      printf("[%-2d] %-*s    %-*s\n", tmp++,
			 title_len, info->title ?: "Unknown",
			 artist_len, info->artist ?: "Unknown");
    }while(iter = g_slist_next(iter));
  }

  index = prompt_for_index(1, tmp - 1);
  if(index == -1)
	return NULL;
  DBGMSG("you picked %d\n", index);

  if(index == 0)  {
	iter = list;
	do{
	  info = (struct lyric_url *)iter->data;
	  if(artist && info->artist)  {
		_artist = strdup(info->artist);
		__sanify(_artist);
		if(! strcasecmp(artist, _artist))  {
		  g_free(_artist);
		  break;
		}
		g_free(_artist);
	  }
	}while(iter = g_slist_next(iter));
	if(! iter)
	  iter = list;
  }else
	iter = g_slist_nth(list, index - 1);

  if(iter)
    lyric_file = fetch_lyric_file_next(iter);

  return lyric_file;
}


static FILE *get_lyric_may_prompt(fetchlyric_t *fl,
								  const gchar *artist, 
								  const gchar *title, 
								  gboolean autoselect)
{
  GSList *list = NULL, *iter;
  gchar *_artist;
  struct lyric_url *info;
  FILE *ret = NULL;

  if(! title)
	return NULL;

  list = fetch_lyric_get_list(fl, title, artist);
  if(g_slist_length(list) > 1 && ! autoselect)
	ret = prompt_pick_lyric(list, artist, title);
  else if(list)  {
	iter = list;
	while(iter)  {
	  info = (struct lyric_url *)iter->data;
	  if(artist && info->artist)  {
		_artist = strdup(info->artist);
		__sanify(_artist);
		if(! strcasecmp(artist, _artist))  {
		  g_free(_artist);
		  break;
		}
		g_free(_artist);
	  }
	  iter = g_slist_next(iter);
	}

	if(! iter)
	  iter = list;

	if(iter)
	  ret = fetch_lyric_file_next(iter);
  }

  fetch_lyric_free_list(list);

  return ret;
}


static gint lyric_search_mode(gint argc, gchar **argv)
{
  const gchar *opath = NULL;
  gchar *artist = NULL, *title = NULL;
  fetchlyric_t *fl = NULL;
  gboolean srtfmt = FALSE;
  gboolean autoselect = FALSE;
  FILE *out, *tmp;
  gint ret;

  if(! argc)
	lyric_search_mode_help();

  for(; argc--; argv++)  {
	if(! strcmp(argv[0], "-a"))  {
	  artist = argv[1];
	  do_if_option(artist, lyric_search_mode_help);
	  argc--;
	  argv++;
	}else   if(! strcmp(argv[0], "-t"))  {
	  title = argv[1];
	  do_if_option(title, lyric_search_mode_help);
	  argc--;
	  argv++;
	}else 	if(! strcmp(argv[0], "-o"))  {
	  opath = argv[1];
	  do_if_option(opath, lyric_search_mode_help);
	  argc--;
	  argv++;
	}else 	if(! strcmp(argv[0], "-u"))
	  autoselect = TRUE;
	else 	if(! strcmp(argv[0], "-s"))
	  srtfmt = TRUE;
	else 	if(! strcmp(argv[0], "-h"))
	  lyric_search_mode_help();
	else
	  lyric_search_mode_help();
  }

  if(! title)  {
	print_err("no title is specified, title is mandatory!\n");
	lyric_search_mode_help();
  }

  if(opath)  {
	out = fopen(opath, "w+");
	if(! out)  {
	  print_err("failed to open file %s for writing: %s\n", opath, strerror(errno));
	  exit(1);
	}
  }else
	out = stdout;

  if(! fetch_lyric_load_all(&fl, NULL, 0, NULL))  {
	print_err("Failed to load lyric fetching plugins.\n");
	exit(1);
  }

  __sanify(artist);
  __sanify(title);

  if(isatty(fileno(out)))
	tmp = get_lyric_may_prompt(fl, artist, title, TRUE);
  else
	tmp = get_lyric_may_prompt(fl, artist, title, autoselect);

  if(tmp)
	/* FIXME: over copy here. */
	file_cat(tmp, out);
  else  {
	print_err("Failed to get lyric of %s %s %s\n", title,
			  artist ? "of artist" : "", artist ? artist : "");
	ret = 1;
  }

  if(! isatty(fileno(out)))
	fclose(out);

  fetch_lyric_unload_all(&fl);

  return ret;
}

static void  id3_sanify_mode_help()
{
  fprintf(stderr,
		  "IDSANE_MODE_COMMAND -h\n"
		  "IDSANE_MODE_COMMAND  -p FILE_LIST\n"
		  "IDSANE_MODE_COMMAND [-a ARTIST] [-t TITLE]  [-l ALBUM] [-y YEAR] [-g GENRE] [-k TRACK]\n"
		  "                    [-c COMMENT] [-r [PATERN]] [-i] [-w] [-o OUTPUT] FILE_LIST\n"
		  "   Modify, Sanify and View mp3 ID3(V1) infomation\n"
		  "OPTIONS:\n"
		  "\t-a    specify artist of the song, optional\n"
		  "\t-t    title of the song\n"
		  "\t-l    album of the song\n"
		  "\t-y    year of the song\n"
		  "\t-g    genre number of the ID3V1 of the mp3 file\n"
		  "\t-k    track of CD where the song is ripped of\n"
		  "\t-c    comment to the song\n"
		  "\t-r    rename the mp3 file according the pattern if given\n"
		  "\t-i    prompt for input if requred pattern can't be replaced\n"
		  "\t-G    list all available genres\n"
		  "\t-O    overwrite output file\n"
		  "\t-W    given -i, prompted missing tags are write back into file\n"
		  "\t-w    write meta info to the input file if -o option is not given\n"
		  "\t-o    file for saving the mp3 file, this will overwrite -r option\n"
		  "\t-p    print all known information\n"
		  "\t-h    show this help message\n"
		  "PATTERN:\n"
		  "\t%%t    title, song name\n"
		  "\t%%a    artist\n"
		  "\t%%l    album\n"
		  "\t%%y    year\n"
		  "\t%%g    genre\n"
		  "\t%%k    track\n"
		  "\t%%c    comment\n"
		  "\t%%d    dir name of the song path\n"
		  "\t%%i    nul, or auto incremented if file name conflicts\n"
		  "   if the specified tag don't exist, and neither specified in the command line,\n"
		  "a blank char will replace instead, or prompted if in interacctive mode. if pattern\n"
		  "is not specified, \"%%t - %%a %%i.mp3\" defaulted.\n");
}


static void id3_print_info(metainfo_t *mi)
{
  const gchar *artist, *title, *album,
	*year, *genre, *track, *comment;
  const gchar *file;

  artist = metainfo_get_artist(mi);
  title = metainfo_get_title(mi);
  album = metainfo_get_album(mi);
  year = metainfo_get_year(mi);
  track = metainfo_get_track(mi);
  genre = metainfo_get_genre(mi);
  comment = metainfo_get_comment(mi);

  file = metainfo_get_path(mi);

  printf("FILE:%s\n", file);
  puts("============================================");
  if(artist)
	printf("ARTIST: %s\n", artist);
  if(title)
	printf("TITLE: %s\n", title);
  if(album)
	printf("ALBUM: %s\n", album);
  if(track)
	printf("TRACK: %s\n", track);
  if(year)
	printf("YEAR: %s\n", year);
  if(genre)
	printf("GENRE: %s\n", genre);
  if(comment)
	printf("COMMENTE: %s\n", comment);
}


static void print_genre_list()
{
  gint i = 0;

  for(; i < genre_list_length; i++)  {
	printf("[%2d]%-.15s  ", i, genre_list[i]);
	if(! ((i + 1)%5))
	  putchar('\n');
  }
}


static void __init_id3v1(ID3V1 *id3, metainfo_t *mi)
{
  const gchar *tmp;

  memset(id3, 0, sizeof(ID3V1));
  id3->header[0] = 'T';
  id3->header[1] = 'A';
  id3->header[2] = 'G';

  if(! mi)  return;
#define __id3_fill_member(attr,len)				\
  do{											\
	tmp = metainfo_get_##attr(mi);				\
	if(tmp)										\
	  strncpy(id3->attr, tmp, len);				\
  }while(0)

  __id3_fill_member(title, 30);
  __id3_fill_member(artist, 30);
  __id3_fill_member(album, 30);
  __id3_fill_member(year, 4);
  __id3_fill_member(comment, 28);

#undef __id3_fill_member
}


/* enum __prompt_flag{ */
/*   PROMPT_FOR_STRING = 0, */
/*   PROMPT_FOR_STRING_ALPHA, */
/*   PROMPT_FOR_STRING_DIGIT, */
/*   PROMPT_FOR_INT, */
/*   PROMPT_FOR_UINT, */
/*   PROMPT_FOR_FLOAT, */
/*   PROMPT_FOR_UFLOAT */
/* }; */

/* static void *prompt_for(const gchar *prompt, const gchar *errmsg, gint flag) */
/* { */



/* } */

/* static gchar *expand_pattern(const gchar *file, */
/* 							 const gchar *pattern, */
/* 							 gboolean interactive, */
/* 							 ID3V1 *id3) */
/* { */
/*   gchar *full_path, *p; */
/*   gchar *pos = pattern; */
/*   gint len = 0, index = 0; */
/*   gboolean autorename = FALSE; */
/*   metainfo_t mi; */
/*   gchar *value; */

/*   gchar *title = NULL, *artist = NULL, *album = NULL, *year = NULL, */
/* 	*genre = NULL, *track = NULL, *comment = NULL; */
/*   gchar *saved_p,saved_pos; */
/*   gint saved_len; */

/*   full_path = g_new0(gchar, 4096); */
/*   p = full_path; */
/*   mi = metainfo_get_metainfo(file, "utf-8", 0); */
/*   if(interactive && id3) */
/* 	__init_id3v1(id3); */

/*   for(;;)  { */
/* 	for(; *pos; pos++)  { */
/* 	  if(*pos != '%')  { */
/* 		*p++ = *pos; */
/* 		len++; */
/* 		continue; */
/* 	  } */

/* 	  pos++; */
/* 	  if(! *pos)  { */
/* 		print_warn("pendding maker %s:%d, ignored.\n", pattern, pos - pattern); */
/* 		break; */
/* 	  } */

/* 	  switch(*pos)  { */


/* 	  } */
/* 	} */
/*   } */

/* } */

/* static gbooblean id3_rename_file(const gchar *file, */
/* 								 const gchar *pattern, */
/* 								 gboolean interactive, */
/* 								 gboolean writeback, */
/* 								 gboolean overwrite) */
/* { */



/* } */


static const gchar *prompt_set_id3(const gchar *prompt, gchar *attr,
								   gint len, gboolean interactive, 
								   gboolean writeback, 
								   const gchar *file)
{
  gchar *value;
  gchar *_prompt;

  if(! interactive)
	return NULL;

  _prompt = g_strdup_printf(prompt, file);
  for(;;)  {
	value = readline(_prompt);
	if(! value)  {
	  g_free(value);
	  g_free(_prompt);
	  return NULL;
	}

	if(! *value)
	  continue;

	if(! strncmp(prompt, "year", 4)  ||
	   ! strncmp(prompt, "track", 5)    )
	  if(! isalldigit(value))  {
		print_warn("invalid value.\n");
		continue;
	  }

	if(writeback && attr)
	  strncat(attr, value, len);

	g_free(_prompt);
	return value;
  }
}


static  void __set_genre(const gchar *genre, char *genre_num);

/* FIXME: messy, tobe rewritten */
static gboolean id3_rename_file(const gchar *file, const gchar *pattern, gboolean interactive, gboolean writeback, gboolean overwrite)
{
  metainfo_t *mi;
  ID3V1 id3;
  gchar name[4097], *p = name, *pindex;
  const gchar *last_pos;
  gint len = 0, i, index = 0, index_len = 0;
  gboolean modified = FALSE;
  gboolean cutted = FALSE;
  const  gchar *value = NULL;
  FILE *fp;

  mi = metainfo_get_metainfo(file, "utf-8", 0);
  __init_id3v1(&id3, mi);
  for(;;)  {
	for(; *pattern; pattern++)  {
	  if(*pattern != '%')  {
		*p = *pattern;
		p++;
		*p = '\0';
		len++;
		continue;
	  }

	  pattern++;
	  if(! *pattern)
		break;
	  switch(*pattern)  {
	  case 't':
		value = metainfo_get_title(mi);
		if(! value)  {
		  value = prompt_set_id3("title for \"%s\" ->", id3.title, 30, interactive, writeback, file);
		  if(value)  {
			modified = TRUE;
			i = strlen(value);
			strncat(p, value, 4096 - len);
			g_free((void *)value);
			value = NULL;
		  }
		}
		break;
	  case 'a':
		value = metainfo_get_artist(mi);
		if(! value)  {
		  value = prompt_set_id3("artist for \"%s\" ->", id3.artist, 30, interactive, writeback, file);
		  if(value)  {
			modified = TRUE;
			i = strlen(value);
			strncat(p, value, 4096 - len);
			g_free((void *)value);
			value = NULL;
		  }
		}
		break;
	  case 'l':
		value = metainfo_get_album(mi);
		if(! value)  {
		  value = prompt_set_id3("album for \"%s\" ->", id3.album, 30, interactive, writeback, file);
		  if(value)  {
			modified = TRUE;
			i = strlen(value);
			strncat(p, value, 4096 - len);
			g_free((void *)value);
		  }
		}
		break;
	  case 'y':
		value = metainfo_get_year(mi);
		if(! value)  {
		  value = prompt_set_id3("year for \"%s\" ->", id3.year, 4, interactive, writeback, file);
		  if(value)  {
			modified = TRUE;
			i = strlen(value);
			strncat(p, value, 4096 - len);
			g_free((void *)value);
			value = NULL;
		  }
		}
		break;
	  case 'g':
		value = metainfo_get_genre(mi);
		if(! value)  {
		  value = prompt_set_id3("genre for \"%s\" ->", NULL, 0, interactive, writeback, file);
		  if(value)  {
			modified = TRUE;
			i = strlen(value);
			__set_genre(value, &id3.genre);
			strncat(p, value, 4096 - len);
			g_free((void *)value);
			value = NULL;
		  }
		}
		break;
	  case 'k':
		value = metainfo_get_track(mi);
		if(! value)  {
		  value = prompt_set_id3("track for \"%s\" ->", NULL, 0, interactive, writeback, file);
		  if(value)  {
			modified = TRUE;
			i = strlen(value);
			sscanf(value, "%hhc", &id3.track);
			strncat(p, value, 4096 - len);
			g_free((void *)value);
			value = NULL;
		  }
		}
		break;
	  case 'c':
		value = metainfo_get_comment(mi);
		if(! value)  {
		  value = prompt_set_id3("comment for \"%s\" ->", id3.comment, 28, interactive, writeback, file);
		  if(value)  {
			modified = TRUE;
			i = strlen(value);
			strncat(p, value, 4096 - len);
			g_free((void *)value);
		  }
		}
		break;
	  case 'i':
		last_pos = pattern - 1;
		pindex = p;
		index_len = len;
		if(index)  {
		  value = g_strdup_printf("[%d]", index);
		  if(value)  {
			i = strlen(value);
			if(i > 4096 - len)  {
			  print_warn("expanded name too long, cutted.\n");
			  cutted = TRUE;
			}
			strncpy(p, value, 4096 - len);
			g_free((void *)value);
			value = NULL;
		  }
		}
		index++;
		break;
	  default:
		print_warn("invalid tag specifier %%%c, ignored.\n", *pattern) ;
		value = NULL;
		break;
	  }

	  if(value)  {
		i = strlen(value);
		strncat(p, value, 4096 - len);
		value = NULL;
	  }

	  if(len + i > 4096)  {
		len = 4096;
		print_warn("expanded name too long, cutted.\n");
		cutted = TRUE;
		break;
	  }else  {
		len += i;
		p += i;
	  }
	  i = 0;
	}

	if(writeback)
	  if(modified)  {
		do{
		  if(interactive)  {
			value = readline("write back tag changes [Y/n]?");
			if(! value)  {
			  printf("please anser \"yes\" or \"no\"\n");
			  continue;
			}

			if(*value)  {
			  if(! strncasecmp(value, "n", 1))  {
				g_free((void *)value);
				value = NULL;
				break;
			  }
			  g_free(value);
			  value = NULL;
			}
		  }

		  fp = fopen(file, "r+");
		  if(fp)  {
			print_info("write back modified id3v1 tag.\n");
			if(! write_id3v1(fp, &id3))
			  print_warn("failed to write back id3v1 tag.\n");
		  }else
			print_warn("failed to open file %s for writting: %s.\n", file, strerror(errno));
		  break;
		}while(1);
	  }

	if(g_file_test(name, G_FILE_TEST_EXISTS))
	  if(overwrite)  {
		if(remove(name))  {
		  print_err("file exists, and can't remove.\n");
		  metainfo_put_metainfo(mi);
		  return FALSE;
		}
	  }else  {
		if(cutted)  {
		  print_err("file name too long, and file exist, aborted.\n");
		  metainfo_put_metainfo(mi);
		  return FALSE;
		}
		pattern = last_pos;
		len = index_len;
		p = pindex;
		continue;
	  }

	print_info("renmaing \"%s\" to \"%s\".\n", file, name);
	metainfo_put_metainfo(mi);
	if(rename(file, name))  {
	  print_err("can't rename file %s to %s: %s.\n", file, name, strerror(errno));
	  return FALSE;
	}

	return TRUE;
  }

  return FALSE;
}


static gint mp3_list_info(GList *list)
{
  GList *iter = list;
  metainfo_t *mi;
  const gchar *path;

  while(iter){
	path = (const gchar *)(iter->data);
	if(g_file_test(path, G_FILE_TEST_EXISTS))  {
	  mi = metainfo_get_metainfo(path, "utf-8", 0);
	  if(mi)  {
		id3_print_info(mi);
		metainfo_put_metainfo(mi);
	  }else
		print_warn("file %s contains no meta info, or malformat.\n", path);
	}else
	  print_warn("file %s don't exist.\n", path);
	iter = g_list_next(iter);
  }

  g_list_free(list);
  return 0;
}


#define __check_argument(str,argc,argv)			\
  do{											\
	if(! argc || argv[1][0] == '-')  {			\
	  printf(str);								\
	  return 1;									\
	}											\
	argc--;										\
	argv++;										\
  }while(0)


static  void __set_genre(const gchar *genre, char *genre_num)
{
  gint i = 0;

  if(genre)
	if(isalldigit(genre))  {
	  sscanf(genre, "%hhc", genre_num);
	  if(*genre_num < 0 || *genre_num >= genre_list_length)  {
		print_warn("genre number out of range [%d, %d].\n", 0, genre_list_length - 1);
		*genre_num = 0;
	  }
	}else  {
	  for(i = 0; i < genre_list_length; i++)
		if(! strcasecmp(genre_list[i], genre))
		  break;
	  if(i < genre_list_length)
		*genre_num = i;
	  else
		print_warn("genre \"%s\" not found, canceled.\n", genre);
	}
}

static gint id3_sanify_mode(gint argc, gchar **argv)
{
  const gchar *_artist = NULL, *_title = NULL, *_album = NULL,
	*_year = NULL, *_genre = NULL, *_track = NULL, *_comment = NULL;
  const gchar *artist = NULL, *title = NULL, *album = NULL,
	*year = NULL, *genre = NULL, *track = NULL, *comment = NULL;
  const gchar *rename_pattern = NULL;
  const gchar *file = NULL;
  GList *file_list = NULL, *iter;
  const gchar *out_path = NULL;
  gboolean print_mode = FALSE;
  metainfo_t *mi = NULL;
  gboolean interactive = FALSE;
  gboolean modify = FALSE;
  gboolean writeback = FALSE;
  gboolean overwrite = FALSE;

  ID3V1 id3;
  gint i;
  FILE *fp, *to;

  for(; argc--; argv++)  {
	if(argv[0][0] != '-')  {
	  file_list = g_list_prepend(file_list, argv[0]);
	  continue;
	}

	if(! strcmp(argv[0], "-a"))  {
	  _artist = argv[1];
	  __check_argument("-a requres an argument\n", argc, argv);
	}else 	if(! strcmp(argv[0], "-t"))  {
	  _title = argv[1];
	  __check_argument("-t requres an argument\n", argc, argv);
	}else 	if(! strcmp(argv[0], "-l"))  {
	  _album = argv[1];
	  __check_argument("-l requres an argument\n", argc, argv);
	}else 	if(! strcmp(argv[0], "-y"))  {
	  _year = argv[1];
	  __check_argument("-y requres an argument\n", argc, argv);
	}else 	if(! strcmp(argv[0], "-g"))  {
	  _genre = argv[1];
	  __check_argument("-g requres an argument\n", argc, argv);
	}else 	if(! strcmp(argv[0], "-k"))  {
	  _year = argv[1];
	  __check_argument("-y requres an argument\n", argc, argv);
	}else 	if(! strcmp(argv[0], "-c"))  {
	  _comment = argv[1];
	  __check_argument("-c requires an argument\n", argc, argv);
	}else 	if(! strcmp(argv[0], "-r"))  {
	  rename_pattern = argv[1];
	  __check_argument("-r requiers an argument\n", argc, argv);
	}else 	if(! strcmp(argv[0], "-i"))  {
	  interactive = TRUE;
	}else 	if(! strcmp(argv[0], "-w"))  {
	  modify = TRUE;
	}else 	if(! strcmp(argv[0], "-o"))  {
	  out_path = argv[1];
	  __check_argument("-o requres an argument\n", argc, argv);
	}else 	if(! strcmp(argv[0], "-p"))  {
	  print_mode = TRUE;
	}else 	if(! strcmp(argv[0], "-O"))  {
	  overwrite = TRUE;
	}else 	if(! strcmp(argv[0], "-W"))  {
	  writeback = TRUE;
	}else 	if(! strcmp(argv[0], "-G"))  {
	  print_genre_list();
	  return 0;
	}else 	if(! strcmp(argv[0], "-h"))  {
	  id3_sanify_mode_help();
	  return 0;
	}else{
	  print_err("invalid option %s.\n", argv[0]);
	  id3_sanify_mode_help();
	  return 1;
	}
  }

  if(! file_list)  {
	print_err("file list argument requred.\n");
	id3_sanify_mode_help();
	return 1;
  }

  if(print_mode)
	return mp3_list_info(file_list);

  if(out_path || modify)  {
	if(g_list_length(file_list) > 1)  {
	  print_err("refuse to duplicate multiple file contents into one file.\n");
	  g_list_free(file_list);
	  return 1;
	}

	file = (const gchar *)(file_list->data);
	g_list_free(file_list);
	if(out_path)  {
	  if(! overwrite)
		if(g_file_test(out_path, G_FILE_TEST_EXISTS))  {
		  print_err("file %s exists.\n", out_path);
		  return 1;
		}

	  to = fopen(out_path, "w+");
	  if(! to)  {
		print_err("can't open file %s for wrtting.\n", out_path);
		return 1;
	  }

	  if(g_access(file, F_OK | R_OK))  {
		print_err("file %s don't exist or permission denied.\n", file);
		return 1;
	  }
	}else  {
	  if(g_access(file, F_OK | R_OK | W_OK))  {
		print_err("file %s don't exist or permission denied.\n", file);
		return 1;
	  }

	  to = fopen(file, "r+");
	  if(! to)  {
		print_err("failed to open file %s.\n", file);
		return 1;
	  }
	}

	mi = metainfo_get_metainfo(file, "utf-8", 0);
	if(mi)  {
#define __id3_copy_attrib(attr,len)				\
	  do{										\
		if(_##attr)								\
		  attr = _##attr;						\
		else									\
		  attr = metainfo_get_##attr(mi);		\
		if(attr)								\
		  strncpy(id3.attr, attr, len);			\
		else									\
		  id3.attr[0] = '\0';					\
	  }while(0)

	  __id3_copy_attrib(title,30);
	  __id3_copy_attrib(artist,30);
	  __id3_copy_attrib(album,30);
	  __id3_copy_attrib(year,4);
	  __id3_copy_attrib(comment,28);

#undef __id3_copy_attrib

	  id3.reserve = 0;
	  id3.track = 0;
	  id3.genre = 0;

	  if(_track)
		track = _track;
	  else
		track = metainfo_get_track(mi);
	  if(track)
		sscanf(track, "%hhc", &id3.track);
	  
	  if(_genre)
		genre = _genre;
	  else
		genre = metainfo_get_genre(mi);
	  __set_genre(genre, &id3.genre);
	  metainfo_put_metainfo(mi);
	}else{
#define __id3_copy_attrib(attr,len)				\
	  do{										\
		if(_##attr)								\
		  strncpy(id3.attr, _##attr, len);		\
		else									\
		  id3.attr[0] = '\0';					\
	  }while(0)

	  __id3_copy_attrib(title, 30);
	  __id3_copy_attrib(artist, 30);
	  __id3_copy_attrib(album, 30);
	  __id3_copy_attrib(year, 4);
	  __id3_copy_attrib(comment, 28);

#undef __id3_copy_attrib
	  id3.reserve = 0;
	  id3.track = 0;
	  id3.genre = 0;

	  if(_track)
		sscanf(_track, "%hhd", &id3.track);
	  if(_genre)
		__set_genre(_genre, &id3.genre);
	}

	memcpy(id3.header, "TAG", 3);
	print_info("write id3v1 tag into file %s.\n", out_path ?: file);
	if(! write_id3v1(to, &id3))  {
	  print_err("failed to write ID3V2 tag.\n");
	  return 1;
	}

	return 0;
  }

  if(! rename_pattern)
	rename_pattern = "%t - %a %i.mp3";

  iter = file_list;
  do{
	file = (const gchar *)iter->data;
	if(g_access(file, F_OK | R_OK))
	  print_warn("file %s don't exist or permission denied, cancelled.\n", file);
	else
	  id3_rename_file(file, rename_pattern, interactive, writeback, overwrite);
  }while(iter = g_list_next(iter));

  g_list_free(file_list);

  return 0;
}



void __attribute__((noreturn))  util_mode_help()
{
  fprintf(stderr, "UTIL_MODE_COMMAND UTIL_MODE_NAME ARGS\n"
		  "\tUTIL_MODE_NAME   builtin utility mode name,currentlly supported: iconvert, lyrics, \n"
		  "\t                 and idsane.\n"
		  "\tARGS             argument for subutil modes, -h for subutil mode help\n");
  exit(1);
}


/* 
 *dispatch commands for two extra ultility mode, ecoding convert mode
 *and lyric search mode
 */
static gint util_mode_dispatch(gint argc, gchar **argv)
{
  if(! argc)
	util_mode_help();

  if(! strcmp(argv[0], "iconvert"))
	return encoding_convert_mode(--argc, ++argv);
  else if(! strcmp(argv[0], "lyrics"))
	return lyric_search_mode(--argc, ++argv);
  else
	util_mode_help();
}


static gint command_help(void *arg);
static gint command_info_mode(void *arg);
static gint command_info_player(void *arg);
static gint command_info_lyric(void *arg);
static gint command_list_mode(void *arg);
static gint command_list_player(void *arg);
static gint command_list_lyrics(void *arg);
static gint command_load_mode(void *arg);
static gint command_load_player(void *arg);
static gint command_load_lyric(void *arg);
//gint command_player(void *arg);
static gint command_quiet(void *arg);
static gint command_verbose(void *arg);
static gint command_version(void *arg);


struct command commands[]  = {
  {"-h" , "--help",        "show this help message",                 command_help,         CMD_EXEC_EXIT },
  {"-Im", "--info-mode",   "show info about the mode plugin",        command_info_mode,    CMD_EXEC_EXIT | CMD_WITH_ARG },
  {"-Ip", "--info-player", "info about the player front end plugin", command_info_player,  CMD_EXEC_EXIT | CMD_WITH_ARG },
  {"-Il", "--info-lyrics", "info about lyric search plugin",         command_info_lyric,   CMD_EXEC_EXIT | CMD_WITH_ARG },
  {"-lm", "--list-mode",   "list available modes",                   command_list_mode,    CMD_EXEC_EXIT | CMD_WITH_ARG | CMD_ARG_OPTIONAL },
  {"-lp", "--list-player", "list supported media player front ends", command_list_player,  CMD_EXEC_EXIT | CMD_WITH_ARG | CMD_ARG_OPTIONAL },
  {"-ll", "--list-lyrics", "list available lyric search plugin",     command_list_lyrics,  CMD_EXEC_EXIT | CMD_WITH_ARG | CMD_ARG_OPTIONAL },
  /*   {"-Lm", "--load-mode",   "load mode plugin from the  file",        command_load_mode,    CMD_WITH_ARG | CMD_EXIT_ON_FAIL }, */
  /*   {"-Lp", "--load-player", "use player plugin or load from  file",   command_load_player,  CMD_WITH_ARG | CMD_EXIT_ON_FAIL }, */
  /*   {"-Ll", "--load-lyric",  "use lyric plugin or load from  file",    command_load_lyric,   CMD_WITH_ARG | CMD_EXIT_ON_FAIL }, */
  //  {"-p",  "--player",      "use the player backend",                 command_player,       CMD_WITH_ARG | CMD_EXIT_ON_FAIL },
  {"-q" , "--quiet",       "supress many info output and be quiet",  command_quiet,        0 },
  {"-v" , "--verbose",     "more verbose infomation output",         command_verbose,      0 },
  {"-V" , "--version",     "about the program and version",          command_version,      0 },
  {NULL , NULL,            NULL,                                     NULL,                 0 }
};

static gint command_help(void *arg)
{
  struct command *cmd = commands;
  struct util_mode *umode = utils;
  gchar i, j, *argument;

  printf( "USAGE:\n"
		  "\t%s -h\n"
		  "\t%s [PROG_ARGS] MODES [MODE_ARGS]\n"
		  "COMAND OPTIONS:\n", PROG_NAME, PROG_NAME);

  while(cmd->short_cmd)  {
	if(cmd->flag & CMD_WITH_ARG)
	  argument = "ARG";
	else
	  argument = "";
	i =  cmd->flag & CMD_ARG_OPTIONAL ? '[' : ' ' ;
	j =  i == '[' ?  ']' : ' ' ;
	printf("\t%-3.3s  %c%-3.3s%c  %s\n", cmd->short_cmd, i, argument, j, cmd->descrip);
	cmd++;
  }

  printf( "\tMODES     specify what mode the program shall run instead\n"
		  "\tMODE_ARGS arguments specific to individual modes, -h for specific mode help\n");

  if(umode->name)  {
	printf("builtin util modes:\n");
	while(umode->name)  {
	  printf("\t%s%*s%s\n", umode->name, 20 - strlen(umode->name), "",  umode->description);
	  umode++;
	}
  }

  return TRUE;
}


static gint command_info_mode(void *arg)
{
  const gchar *file = (const gchar *)arg;
  struct ui_descrip *ui;
  struct submode *mode = NULL;
  void *handle = NULL;

  if(g_file_test(file, G_FILE_TEST_IS_REGULAR))  {
	ui = load_ui_file(file, &handle);
	if(! ui)  {
	  print_err("Failed to load %s\n", file);
	  return 1;
	}
  }else  {
	if(! modes)
	  modes = build_mode_list(NULL);
	mode = find_ui(modes, file);
	if(! mode)  {
	  print_err("Could not find UI module naming %s.\n", file);
	  return 1;
	}

	ui = load_ui(mode);
	if(! ui)  {
	  print_err("Could not load UI module %s naming %s.\n", mode->path, file);
	  return 1;
	}

	file = mode->path;
  }

  printf("UI module: %s %s\n", ui->name, ui->version);
  printf("PATH: %s\n", file);
  printf("AUTHOR: %s\t MAIL:%s\n", ui->author, ui->mail);
  printf("DESCRIPTION:\n\t%s\n", ui->description);

  if(handle)
	dlclose(handle);
  if(mode)
	unload_ui(mode);

  return 0;
}


static gint command_info_player(void *arg)
{
  const gchar *file = (const gchar *)arg;
  const  struct client_plugin_info *player;

  if(g_file_test(file, G_FILE_TEST_IS_REGULAR))  {
	player = load_client_file(file);
	if(! player)  {
	  print_err("Cound not load plugin %s\n", file);
	  return 1;
	}
  }else if(! (player = load_client(file)))  {
	print_err("Cound not load any plugin naming %s\n", file);
	return 1;
  }

  printf("PLAYER BACKEND: %s %s\n", player->player_name, player->version);
  printf("AUTHOR: %s \t MAIL: %s\n", player->author, player->mail);
  printf("DESCRIPTION:\n\t%s\n", player->description);

  unload_client(player->player_name, NULL);

  return 0;
}


static gint command_info_lyric(void *arg)
{
  const gchar *file = (const gchar *)arg;
  fetchlyric_t *fl = NULL;
  struct fetchlyric_plugin_descrip *plugin;
  gchar **name;

  if(g_file_test(file, G_FILE_TEST_IS_REGULAR))  {
	if(! fetch_lyric_load_plugin_from_file(&fl, file, 0, NULL))  {
	  print_err("Can't loadd plugin from %s\n", file);
	  return 1;
	}
  }else if(! fetch_lyric_load_plugin(&fl, file, NULL, 0, NULL))   {
	print_err("Can't loadd plugin from %s\n", file);
	return 1;
  }

  name = fetch_lyric_plugin_list(fl);
  plugin = fetch_lyric_get_descrip(fl, *name);
  g_strfreev(name);

  printf("FETCHLYRIC PLUGIN: %s %s %s\n", plugin->name, 
		 plugin->version, plugin->islocal ? "" : "(Networked)");
  printf("AUTHOR: %s \t MAIL: %s\n", plugin->author, plugin->mail);
  printf("DESCRIPTION:\n\t%s\n", plugin->description);

  fetch_lyric_unload_all(&fl);

  return 0;
}


static void __print_mode_list(GList *list)
{
  const gchar *fname;
  struct submode *cur;
  gint i = 1;

  do{
	cur = (struct submode *)list->data;
	fname = strrchr(cur->path, '/');
	if(! fname)
	  fname = cur->path;
	printf("<%d>%-10s    %-20s  %s\n", i++, cur->name, fname, cur->descrip);
  }while(list = g_list_next(list));
}


static gint command_list_mode(void *arg)
{
  const  gchar *dir = (gchar *)arg;

  if(dir)  {
	if(modes)
	  modes = destroy_mode_list(modes);
	modes = build_mode_list(dir);
	if(modes)  {
	  printf("UI modules in %s:\n", dir);
	  __print_mode_list(modes);
	  modes = destroy_mode_list(modes);
	  return 0;
	}else  {
	  printf("No UI plugins found in %s\n", dir);
	  return 1;
	}
  }else  {
	if(modes)  {
	  printf("UI modules in %s:\n", UI_BASEDIR);
	  __print_mode_list(modes);
	}else
	  printf("No UI plugin installed in %s\n", UI_BASEDIR);
  }

  return 0;
}


static void __free_data(gpointer data, gpointer user_data)
{
  return g_free(data);
}


static gint __print_player_list(const gchar *path)
{
  struct dirent *ent;
  const struct client_plugin_info *clt;
  const gchar *fpath;
  gint x = 0, y = 0;
  DIR *dir;
  GList *list = NULL;

  dir = opendir(path);
  if(! dir)  {
	print_err("Failed to open %s:%s\n", path, strerror(errno));
	return 1;
  }

  while(ent = readdir(dir))  {
	if(! strcmp(ent->d_name, ".")  ||
	   ! strcmp(ent->d_name, "..")  )
	  continue;
	fpath = g_strdup_printf("%s/%s", path, ent->d_name);
	clt = load_client_file(fpath);
	if(clt)  {
	  if(! g_list_find_custom(list, clt->player_name, g_strcmp0))  {
		list = g_list_prepend(list, g_strdup(clt->player_name));
		x++;
		if(x == 1)
		  printf("Found player Front ends in %s:\n", path);
		printf("<%d>%-15s   %-23s  %s\n", x, clt->player_name, ent->d_name, clt->description);
	  }

	  if(! unload_client(clt->player_name, NULL))
		print_err("unable to unload client:%s: %s: %d\n", __FILE__, __func__, __LINE__);
	}else  {
	  print_verb("%s seems not a valid plugin.\n", ent->d_name);
	  y++;
	}

	g_free((void *)fpath);
  }

  g_list_foreach(list, __free_data, NULL);
  g_list_free(list);

  if(! x && ! y)
	printf("No plugin found in %s.\n", path);

  return 0;
}


static gint command_list_player(void *arg)
{
  const  gchar *dir = (gchar *)arg;

  if(dir)
	return __print_player_list(dir);
  else 
	return __print_player_list(CLIENT_BASEDIR);
}


static gint 	  __print_lyric_plugin_list(const gchar *dir)
{
  gchar **i, **plugins;
  gint j = 1;
  fetchlyric_t *fl = NULL;
  struct fetchlyric_plugin_descrip *descrip;

  if(fetch_lyric_load_all(&fl, dir, 0, NULL))  {
	plugins = fetch_lyric_plugin_list(fl);
	if(plugins)  {
	  printf("Found lyric plugins in %s:\n", dir);
	  for(i = plugins; *i; i++)  {
		descrip = fetch_lyric_get_descrip(fl, *i);
		if(descrip)
		  /* FIXME: lost associated file name. */
		  printf("<%d>%-15s    %s\n", i - plugins + 1, descrip->name, descrip->description);
		else
		  print_bug("Failed to get plugin descrip:%s:%s:%d\n",
					__FILE__, __func__, __LINE__);
	  }
	  g_strfreev(plugins);
	}
	fetch_lyric_unload_all(&fl);
	return 0;
  }

  return 1;
}


static gint command_list_lyrics(void *arg)
{
  const gchar *dir = (const gchar *)arg;
  gchar **plugins;
  gint ret;

  if(dir)
	return __print_lyric_plugin_list(dir);
  else
	return __print_lyric_plugin_list(FETCHLYRIC_BASEDIR);
}


static gint command_load_mode(void *arg)
{
  const gchar *file = (const gchar *)arg;
  struct ui_descrip *ui;
  struct submode *mode;
  void *handle;

  ui = load_ui_file(file, &handle);
  if(! ui)  {
	print_err("Couldn't load UI module: %s.\n", file);
	return 1;
  }

  mode = g_new(struct submode, 1);
  mode->handle = NULL;
  mode->path = strdup(file);
  mode->name = strdup(ui->name);
  mode->descrip = strdup(ui->description);
  dlclose(handle);
  modes = g_list_prepend(modes, mode);

  return 0;  
}


static gint command_load_player(void *arg)
{
  const gchar *file = (const gchar *)arg;

  if(g_file_test(file, G_FILE_TEST_EXISTS))
	player_plugin_file = file;
  else
	player_plugin = file;

  return 0;
  /*   struct client_plugin_info *info; */

  /*   if(! file) */
  /* 	return 1; */
  /*   info = load_client_file(file); */
  /*   if(info)  { */
  /* 	player = client->player_name; */
  /* 	return 0; */
  /*   } */

  /*   fprintf(stderr, "** ERROR: %s seem not a valid player front end plugin.\n", file); */
  /*   return 1; */
}


static gint command_load_lyric(void *arg)
{
  const gchar *file = (const gchar *)arg;

  if(g_file_test(file, G_FILE_TEST_IS_REGULAR))
	lyric_plugin_file = file;
  else
	lyric_plugin = file;

  return 0;
}


/* use this player front end for playing, be sure it exists and can be
   loaded */

/* static gint command_player(void *arg) */
/* { */
/*   const gchar *client = (const gchar *)arg; */

/*   if(! arg) */
/* 	return 1; */
/*   if(load_client(client))  { */
/* 	player = client; */
/* 	if(unload_client(client, NULL)) */
/* 	  fprintf(stderr, "** BUG: unable to unload client:%s: %s: %d\n", __FILE__, __func__, __LINE__); */
/* 	return 0; */
/*   } */

/*   return 1; */
/* } */


static gint command_quiet(void *arg)
{
  quiet = TRUE;
  return 0;
}


static gint command_verbose(void *arg)
{
  verbose = TRUE;
  return 0;
}


static gint command_version(void *arg)
{
  printf(PROG_NAME " " PROG_VERSION " Copyright (c) 2009 All right reserved.\n"
		 GREETING);

  return 0;
}


int main(int argc, char **argv)
{
  config_t *config;
  struct command *cmd;
  const gchar *arg;
  gint ret;
  const gchar *args = NULL;
  struct ui_descrip *ui;
  struct util_mode *umode = utils;

  config = load_config(NULL, NULL, PROG_CONF, MAIN_DEFAULT_CONFIG, 0, TRUE);
  modes = build_mode_list(NULL);

  for(argc--, argv++ ; argc--; argv++)  {
	if(argv[0][0] != '-')  {
	  mode = argv[0];
	  argv++;
	  break;
	}

	for(cmd = commands; cmd->short_cmd; cmd++)
	  if(! strcmp(argv[0], cmd->short_cmd)  ||
		 ! strcmp(argv[0], cmd->long_cmd)      )  {
		if(cmd->flag & CMD_WITH_ARG)  {
		  if(argc > 0 && argv[1][0] != '-')  {
			arg = argv[1];
			argc--;
			argv++;
		  }else if(cmd->flag & CMD_ARG_OPTIONAL)
			arg = NULL;
		  else  {
			print_err("option %s needs an argument.\n", argv[0]);
			command_help(NULL);
			return 1;
		  }
		}else
		  arg = NULL;

		if(! cmd->fn)  {
		  print_info("option %s is not implemented yet, ignored.\n", argv[0]);
		  continue;
		}

		ret = (cmd->fn)((void *)arg);
		if(cmd->flag & CMD_EXEC_EXIT)
		  return ret;

		if(ret)
		  if(cmd->flag & CMD_EXIT_ON_FAIL)
			return ret;

		break;
	  }

	if(! cmd->short_cmd)  {
	  print_err("invalid option: %s\n", argv[0]);
	  command_help(NULL);
	  return 1;
	}  
  }

  greeting();

  ui = load_ui_name(modes, mode);
  if(! ui)  {
	while(umode->name)  {
	  if(! strcmp(umode->name, mode))
		if(umode->fn)
		  return (umode->fn)(argc, argv);
		else  {
		  printf("Sorry, %s is not implemented yet.\n");
		  return 1;
		}
	  umode++;
	}

	print_err("module %s not found, abort.\n", mode);
	return -1;
  }

  build_arg_str(0, &args,
				"client_backend", player_plugin,
				"client_backend_file", player_plugin_file,
				"lyric_plugin", lyric_plugin,
				"lyric_plugin_file", lyric_plugin_file,
				"client_name", PROG_NAME,
				NULL);

  ret = ui_run(ui, args, argc, argv);

  unload_ui_all(modes);
  free_config(config);

  return ret;
}

